﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="ValueTypeSettingDescriptor.cs" company="Radical Research Ltd">
//   Copyright (c) Radical Research Ltd. The MIT License (MIT)
// </copyright>
// <summary>
//   Stores numeric values as Base46
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace RadicalResearch.MiniState.Descriptors
{
    using System;
    using System.Runtime.InteropServices;

    /// <summary>Stores numeric values as Base46</summary>
    public class ValueTypeSettingDescriptor : SettingDescriptorBase
    {
        #region Constants and Fields

        /// <summary>
        ///   type of value
        /// </summary>
        private readonly Type type;

        #endregion

        #region Constructors and Destructors

        /// <summary>Initializes a new instance of the <see cref="ValueTypeSettingDescriptor"/> class.</summary>
        /// <param name="valueKey">The value key.</param>
        /// <param name="isValidated">if set to <c>true</c> [is validated].</param>
        /// <param name="type">Type of the value.</param>
        public ValueTypeSettingDescriptor(string valueKey, bool isValidated, Type type)
            : base(valueKey, isValidated)
        {
            if (!type.IsValueType)
            {
                throw new ArgumentException("value must be a value type", "type");
            }

            this.type = type;
        }

        #endregion

        #region Public Methods

        /// <summary>Reads the value.</summary>
        /// <param name="values">The values.</param>
        /// <returns>value as an object</returns>
        public override object ReadValue(IValueCollection values)
        {
            string b64 = values[this.ValueKey];
            if (null != b64)
            {
                byte[] bytes = Convert.FromBase64String(b64);
                object number = GetValueFromBytes(bytes, this.type);
                return number;
            }

            return null;
        }

        /// <summary>Writes the value.</summary>
        /// <param name="values">The values.</param>
        /// <param name="value">The value.</param>
        public override void WriteValue(IValueCollection values, object value)
        {
            byte[] bytes = GetBytesFromValue(value);
            string b64 = Convert.ToBase64String(bytes);
            values[this.ValueKey] = b64;
        }

        #endregion

        #region Methods

        /// <summary>Gets the bytes from value.</summary>
        /// <param name="value">The value.</param>
        /// <returns>the bytes of managed object.</returns>
        private static byte[] GetBytesFromValue(object value)
        {
            // Initialize unmanaged memory to hold the struct.
            int size = Marshal.SizeOf(value);
            IntPtr pointer = Marshal.AllocHGlobal(size);
            byte[] bytes = new byte[size];
            try
            {
                // Copy the struct to unmanaged memory.
                Marshal.StructureToPtr(value, pointer, false);
                Marshal.Copy(pointer, bytes, 0, size);
                return bytes;
            }
            finally
            {
                // Free the unmanaged memory.
                Marshal.FreeHGlobal(pointer);
            }
        }

        /// <summary>Gets a value type object from an array of bytes.</summary>
        /// <param name="valueBytes">The array of bytes that represent the value in memory.</param>
        /// <param name="valueType">Type of the value to return.</param>
        /// <returns>value type created from bytes</returns>
        private static object GetValueFromBytes(byte[] valueBytes, Type valueType)
        {
            GCHandle pinnedBytes = GCHandle.Alloc(valueBytes, GCHandleType.Pinned);
            try
            {
                object value = Marshal.PtrToStructure(pinnedBytes.AddrOfPinnedObject(), valueType);
                return value;
            }
            finally
            {
                pinnedBytes.Free();
            }
        }

        #endregion
    }
}